#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <functional>
#include <unordered_map>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define SIZE 1024

enum{
    SOCK_ERR = 1,
    BIND_Err
};

uint16_t defaultport =  8081;
std::string defaultip = "0.0.0.0";

// 参数是 const std::string& 
// 返回值：std::string
// 函数指针
using func_t = std::function<std::string(const std::string&)>;

class UdpServer
{
private:
    int sockfd_;        // sock 网络文件描述符
    uint16_t port_;     // 端口号
    std::string ip_;    // ip地址
    bool isrunning_;    // 判断该服务器是否在运行
    // 用户聊天室
    std::unordered_map<std::string, sockaddr_in> userinfo_; // 维护一张用户信息表。一个用户维护一个套接字
public:
    UdpServer(const uint16_t &port = defaultport, std::string &ip = defaultip)
        :sockfd_(-1), port_(port),ip_(ip),isrunning_(false)
    {}
    ~UdpServer()
    {
        close(sockfd_);
    }
private:
    // 1.创建套接字
    void Socket()
    {
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
        if(sockfd_ < 0){
            exit(SOCK_ERR);
        }
    }
    // 2.绑定套接字
    void Bind()
    {
        struct sockaddr_in local;
        bzero(&local, sizeof(local));   // 清理 local 空间
        // 填充网络套接字字段
        local.sin_family = AF_INET;
        local.sin_port = htons(port_);
        local.sin_addr.s_addr = inet_addr(ip_.c_str());
        // 填充完毕，开始绑定
        int n = bind(sockfd_, (const sockaddr*)&local, sizeof(local));
        if(n < 0){
            exit(BIND_Err);
        }
    }
    // 检测该用户是否在聊天室中
    void CheckUser(const sockaddr_in &client, const std::string &clientip, const uint16_t &clientport)
    {
        auto pos = userinfo_.find(clientip);
        if(pos == userinfo_.end())
        {
            // 没有该用户,就将该用户添加到聊天室中
            userinfo_.insert({clientip, client});
            printf("[%s-%d] Add in the chat room\n", clientip.c_str(), clientport);
        }
    }
    // 将 client_ip:client_port 发送的信息广播给所有的聊天室成员
    void BroadCast(const std::string &info, const std::string &clientip, const uint16_t &clientport)
    {
        for(auto &user : userinfo_)
        {
            std::string message = "[";
            message += clientip;
            message += ": ";
            message += std::to_string(clientport);
            message += "]# ";

            message += info;
            socklen_t len = sizeof(user.second);
            sendto(sockfd_, message.c_str(), message.size(), 0, (const sockaddr*)&(user.second), len);
        }
    }
public:
    void Init()
    {
        // 1.创建套接字
        Socket();
        // 2.绑定套接字
        Bind();
    }
    // void Run(func_t func)   // 参数就是数据处理函数
    void Run()
    {
        isrunning_ = true;  // 开始运行 udpsocket 服务器
        while (isrunning_)
        {
            // 从套接字中读取信息（recvfrom）
            char buffer[SIZE];
            // bzero(buffer, sizeof(buffer));
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int ret = recvfrom(sockfd_, buffer, sizeof(buffer)-1, 0, (sockaddr*)&client, &len);
            if(ret < 0)
            {
                // 读取数据错误
                continue;
            }
            // 读取数据成功
            buffer[ret] = 0;
            // printf("clinet say@ %s\n", buffer);

            // // 对 用户写入的数据 进行处理
            // std::string info = buffer;
            // std::string echo_string = func(info);

            // // 处理完用户的数据之后，将数据处理的结果发送给用户
            // int n = sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (const sockaddr*)&client, len);
            // if(n < 0)
            // {
            //     // 发送信息给 用户 失败
            //     continue;
            // }

            // 用户聊天室
            uint16_t client_port = ntohs(client.sin_port);      // 获取client 端的端口号
            std::string client_ip = inet_ntoa(client.sin_addr); // 获取 client 端的 ip 地址
            // 检查该用户是否在聊天室中, 如果没有就将他添加到聊天室中
            CheckUser(client, client_ip, client_port);  
            std::string info = buffer;
            // 将 client_ip:client_port 发送的信息广播给所有的聊天室成员
            BroadCast(info, client_ip, client_port);
        }
        
    }
};
